﻿using System;
using Faml.Frontend;
using System.Linq.Expressions;
using Faml.Optimization;
using Faml.LinqCompiler;
using System.Text;
using Faml;
using dotnetcdf;
using System.Collections.Generic;
using Faml.Compiler;

public class NetCDFCommand : ExternalCommand
{
    /// <summary>Identifier of NetCDF data set</summary>
    private int ncId = 0;
    private Dictionary<string, int> dims = new Dictionary<string, int>();
    private Dictionary<int, object> vars = new Dictionary<int, object>();


    public NetCDFCommand()
        : base("netcdf")
    { /* Nothing to do here */ }

    public override void Parse(Parser parser, RuntimeEnvironment runtime)
    {
        dims.Clear();
        vars.Clear();
        string fileName = FamlProgram.Evaluate(runtime, runtime.Precompile(parser)).ToString();
        NetCDF.nc_create(ref fileName, 0, ref ncId);
        if (!parser.MatchDelimiter("{"))
            throw new Exception("Syntax error in NetCDF content definition");
        parser.MoveNext();
        while (true)
        {

            if (parser.MatchDelimiter("}"))
            {
                parser.MoveNext();
                break;
            }
            else if (parser.TokenType == TokenType.Ident)
            {
                string ident = parser.TokenText;
                parser.MoveNext();
                if (ident == "dim")
                    ParseDimension(parser, runtime);
                else if (ident == "var")
                    ParseVariable(parser, runtime);
                else
                    throw new Exception("Unknown type of NetCDF content");
            }
            else
                throw new Exception("Syntax error in NetCDF content definition");
        }
        NetCDF.nc_enddef(ncId);
        foreach (KeyValuePair<int, object> var in vars)
        {
            int varId = var.Key;
            object varData = var.Value;
            Array varArray = varData as Array;
            if (varArray == null)
                NetCDF.nc_put_var_double(ncId, varId, new double[] { (double)varData });
            else
            {
                double[] vals = new double[varArray.Length];
                int[] indices = new int[varArray.Rank];
                for (int i = 0; i < varArray.Length; i++)
                {
                    vals[i] = (double)varArray.GetValue(indices);
                    int j = 0;
                    while (true)
                    {
                        indices[j]++;
                        if (indices[j] >= varArray.GetLength(j))
                        {
                            indices[j] = 0;
                            if (j < varArray.Rank - 1)
                                j++;
                            else
                                break;
                        }
                        else
                            break;
                    }
                }
                NetCDF.nc_put_var_double(ncId, varId, vals);
            }
        }
        NetCDF.nc_close(ncId);
    }

    private void ParseDimension(Parser parser, RuntimeEnvironment runtime)
    {
        string dimName = parser.TokenText;
        parser.MoveNext();
        if (!parser.MatchDelimiter("="))
            throw new Exception("Equal sign is expected in dimension definition");
        parser.MoveNext();
        object lengthObj = FamlProgram.Evaluate(runtime, runtime.Precompile(parser));
        if (!(lengthObj is int))
            throw new Exception("Dimension length should be integer");
        int length = (int)lengthObj;
        int dimId = 0;
        NetCDF.nc_def_dim(ncId, ref dimName, length, ref dimId);
        dims.Add(dimName, dimId);
    }

    private void ParseVariable(Parser parser, RuntimeEnvironment runtime)
    {
        string varName = parser.TokenText;// runtime.Evaluate(parser).ToString();
        parser.MoveNext();
        List<int> varDims = new List<int>();
        if (parser.MatchDelimiter("["))
        {
            parser.MoveNext();
            while (true)
            {
                if (parser.TokenType != TokenType.Ident)
                    throw new Exception("Expecting dimension name");
                if (dims.ContainsKey(parser.TokenText))
                    varDims.Add(dims[parser.TokenText]);
                else
                    throw new Exception("Undefined dimension name");
                parser.MoveNext();
                if (parser.MatchDelimiter("]"))
                    break;
                else if (!parser.MatchDelimiter(","))
                    throw new Exception("Syntax error in variable definition");
                else
                    parser.MoveNext();
            }
            parser.MoveNext();
            if (!parser.MatchDelimiter("="))
                throw new Exception("Equal sign is expected in variable definition");
            parser.MoveNext();
            object varData = FamlProgram.Evaluate(runtime, runtime.Precompile(parser));
            int varRank = GetRank(varData);
            if (varRank != varDims.Count)
                throw new Exception("Dimension of data does not match specified dimensions");
            int varId = 0;
            NetCDF.nc_def_var(ncId, ref varName, NetCDF.nc_type.NC_DOUBLE, varRank,
                varDims.ToArray(), ref varId);
            vars.Add(varId, varData);
        }
    }

    private int GetRank(object obj)
    {
        Array a = obj as Array;
        if (a != null)
            return a.Rank;
        else
            return 0; // Scalar
    }
}